/*
 * 代号：凤凰
 * http://www.jphenix.org
 * 2017年1月30日
 * V4.0
 */
package com.jphenix.kernel.classloader;

//#region 【引用区】
import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import com.jphenix.kernel.objectloader.util.JarResourceVO;
import com.jphenix.share.lang.SListMap;
import com.jphenix.share.tools.JarTools;
import com.jphenix.share.util.SFilesUtil;
import com.jphenix.share.util.StringUtil;
import com.jphenix.standard.docs.BeanInfo;
import com.jphenix.standard.docs.ClassInfo;
import com.jphenix.standard.docs.Running;
import com.jphenix.ver.PhenixVer;
//#endregion

//#region 【说明区】
/**
 * 内置资源加载器
 * 
 * 程序加载时，先加载以前时间的资源包，如果加载时遇到同名同路径
 * 的文件，则将覆盖以前的文件
 *
 * 该类仅管理SDK包中 /resources 文件夹中的内容
 *
 *
 * 2018-09-05 这是内核调用的类，在调用时还没做初始化，所以去掉基类ABase父类
 * 2024-08-09 简化了jar内部资源管理，废弃了.jpxres文件管理（从来没用过，以后也不会用）
 * 2024-08-17 适配了嵌入Spring架构
 * 
 * @author MBG
 * 2017年1月30日
 */
//#endregion
@ClassInfo({"2024-08-17 23:20","内置资源加载器"})
@BeanInfo({"resourcesloader"})
@Running({"99","init","","release"})
public class ResourcesLoader {

	//#region 【变量声明区】
	//由于 压缩包中不保存单独的目录信息，
	//需要从文件中提取出目录信息，判断压缩包中是否存在该目录
	private List<String> pathList = null;
	private SListMap<JarResourceVO> entriesMap = null; //文件元素容器
	private List<ZipFile> jarList = null; //符合条件的压缩包序列
	//资源包中的配置信息对照容器   按照时间先后顺序覆盖同名参数
	private Map<String,Map<String,String>> resPropertyMap = new HashMap<String,Map<String,String>>();
	//#endregion
	
	//#region 【构造函数】
	/**
	 * 构造函数
	 * @author MBG
	 */
	public ResourcesLoader() {
		super();
		
		jarList    = new ArrayList<ZipFile>();
		entriesMap = new SListMap<JarResourceVO>();
		pathList   = new ArrayList<String>();
		
		loadSelf(); // 加载内部资源
	}
	//#endregion
	
	//#region getPropertyMap(title) 获取指定段的参数对照容器
	/**
	 * 获取指定段的参数对照容器
	 * @param title 参数段主键
	 * @return 参数对照容器
	 * 2017年2月17日
	 * @author MBG
	 */
	public Map<String,String> getPropertyMap(String title){
		if(title==null) {
			title = "";
		}
		//获取返回值
		Map<String,String> reMap = resPropertyMap.get(title);
		if(reMap==null) {
			return new HashMap<String,String>();
		}
		return reMap;
	}
	//#endregion
	
	//#region hasFile(subPath) 是否存在指定文件
	/**
	 * 是否存在指定文件
	 * @param subPath 文件相对路径
	 * @return 是否存在指定文件
	 * 2017年2月1日
	 * @author MBG
	 */
	public boolean hasFile(String subPath) {
		return entriesMap.containsKey(subPath);
	}
	//#endregion
	
	//#region hasPath(subPath) 压缩包中是否存在该路径
	/**
	 * 压缩包中是否存在该路径
	 * @param subPath 待判断路径
	 * @return 是否存在该路径
	 * 2017年2月1日
	 * @author MBG
	 */
	public boolean hasPath(String subPath) {
		return pathList.contains(subPath);
	}
	//#endregion
	
	//#region getFileTime(subPath) 获取文件最后更新时间
	/**
	 * 获取文件最后更新时间
	 * @param subPath 文件相对路径
	 * @return 文件最后更新时间
	 * 2017年2月1日
	 * @author MBG
	 */
	public long getFileTime(String subPath) {
		//获取内部文件
		JarResourceVO jVO = entriesMap.get(subPath);
		if(jVO==null) {
			return 0;
		}
		return jVO.entrie.getTime();
	}
	//#endregion
	
	//#region getFile(subPath) 获取压缩文件元素读入流
	/**
	 * 获取压缩文件元素读入流
	 * @param subPath 压缩文件相对路径
	 * @return 对应的文件读入流
	 * @throws Exception 异常
	 * 2017年2月1日
	 * @author MBG
	 */
	public InputStream getFile(String subPath) throws Exception {
		//获取内部文件
		JarResourceVO jVO = entriesMap.get(subPath);
		if(jVO==null) {
			throw new Exception("Not Find The Jar File From Path:["+subPath+"]");
		}
		return jVO.jarFile.getInputStream(jVO.entrie);
	}
	//#endregion
	
	//#region getResourceVO(subPath) 获取对应的压缩资源信息容器
	/**
	 * 获取对应的压缩资源信息容器
	 * @param subPath 相对路径
	 * @return 压缩资源信息容器
	 * @throws Exception 异常
	 * 2017年2月22日
	 * @author MBG
	 */
	public JarResourceVO getResourceVO(String subPath) throws Exception {
		//获取内部文件
		return entriesMap.get(subPath);
	}
	//#endregion
	
	//#region getSubPathList(baseSubPath) 返回与路径开头匹配的自路径序列
	/**
	 * 返回与路径开头匹配的自路径序列
	 * @param baseSubPath 开头路径
	 * @return 匹配路径序列
	 * 2017年1月31日
	 * @author MBG
	 */
	public List<String> getSubPathList(String baseSubPath){
		if(!baseSubPath.startsWith("/")) {
			baseSubPath = "/"+baseSubPath;
		}
		//构建返回值
		List<String> reList = new ArrayList<String>();
		for(String path:entriesMap.keys()) {
			if(path.startsWith(baseSubPath)) {
				reList.add(path);
			}
		}
		return reList;
	}
	//#endregion
	
	//#region getChildSources(baseSubPath) 获取指定路径下的所有资源信息（包含其子路径下的资源）
	/**
	 * 获取指定路径下的所有资源信息（包含其子路径下的资源）
	 * @param baseSubPath 指定路径
	 * @return            执行路径下的所有资源
	 */
	public List<JarResourceVO> getChildSources(String baseSubPath) {
		if(!baseSubPath.startsWith("/")) {
			baseSubPath = "/"+baseSubPath;
		}
		// 构建返回值
		List<JarResourceVO> reList = new ArrayList<>();
		for(String path:entriesMap.keys()) {
			if(path.startsWith(baseSubPath)) {
				reList.add(entriesMap.get(path));
			}
		}
		return reList;
	}
	//#endregion
	
	//#region 【内部方法】 loadSelf() 单jar包模式，加载资深jar包中的资源
	/**
	 * 单jar包模式，加载自身jar包中的资源
	 * 2024年8月1日
	 * @author MBG
	 */
	private void loadSelf() {
        //获取当前类URL
        URL url = (this.getClass().getProtectionDomain()).getCodeSource().getLocation();
      //从Class中获取到的路径都是UTF-8编码格式的
        String jarPath = null;
        try {
        	jarPath = URLDecoder.decode(url.getPath(),"UTF-8");
        }catch(Exception e) {
        	e.printStackTrace();
        	return;
        }
        
        if(jarPath.startsWith("file:")) {
        	jarPath = jarPath.substring(5);
        }

        ZipFile jarFile = null; // 资源jar包
        
        //#region 如果SDK包是放在Spring组合包内
        if(StringUtil.charCount(jarPath,'!')>1) {
        	/*
        	 * 不确定SDK包随后能有多大，一次性加载到内存中比较冒险
        	 * 所以还是稳妥些，将SDK包解压出来，再从解压出来的SDK
        	 * 包中将资源获取出来
        	 * 
        	 * 先判断是不是已经解包出来了
        	 */
        	int point = jarPath.indexOf("!");
        	// jar包根路径
        	String basePath = jarPath.substring(0,point);
        	// 解包后的文件路径
        	String sdkSubPath = "/file_sources/sdk/"+PhenixVer.JAR_FILE_NAME;
        	// 解包后的文件对象
        	File sdkFile = new File((new File(basePath)).getParentFile().getPath()+File.separator+sdkSubPath);
        	
        	//#region 已经解包
        	if(sdkFile.exists()) {
        		jarFile = getZipFile(sdkFile.getPath());
        	}
        	//#endregion
        	
        	//#region 准备执行解包
        	else {
        		// 执行解包
        		JarTools.undoJarFirstFile(jarPath,sdkFile);
        		// 构建解包后的sdk文件对象
        		jarFile = getZipFile(sdkFile);
        	}
        	//#endregion
        }
        //#endregion
        
        //#region 自身的jar文件
        else {
        	jarFile = getZipFile(jarPath);
        }
        //#endregion
        
        if(jarFile==null) {
        	return;
        }
		//压缩包内容列表迭代
		Enumeration<? extends ZipEntry> entries = jarFile.entries();
		jarList.add(jarFile);
		ZipEntry ele;     // 元素 
		String   subPath; // 相对路径
		String   path;    // 文件路径 
		while(entries.hasMoreElements()) {
			ele = entries.nextElement();
			subPath = ele.getName();
			if(!subPath.startsWith("resources/")) {
				continue;
			}
			subPath = subPath.substring(9);
			entriesMap.put(subPath,new JarResourceVO(jarFile,ele));
			
			path = SFilesUtil.getFilePath(subPath);
			if(!pathList.contains(path)) {
				pathList.add(path);
			}
		}
	}
	//#endregion
	
	//#region release() 释放资源
	/**
	 * 释放资源
	 * 2017年1月31日
	 * @author MBG
	 */
	public void release() {
		if(jarList!=null) {
			for(ZipFile ele:jarList) {
				try {
					ele.close();
				}catch(Exception e) {}
				ele = null;
			}
		}
		jarList = null;
	}
	//#endregion
	
	//#region 【内部方法】getZipFile() 获取压缩文件对象
	/**
	 * 获取压缩文件对象
	 * @param path 文件全路径
	 * @return 压缩文件对象
	 * 2017年1月30日
	 * @author MBG
	 */
	private ZipFile getZipFile(String path) {
		try {
			return new ZipFile(new File(path));
		}catch(Exception e) {}
		return null;
	}
	//#endregion
	
	//#region 【内部方法】getZipFile() 获取压缩文件对象
	/**
	 * 获取压缩文件对象
	 * @param file 文件对象
	 * @return 压缩文件对象
	 * 2024-08-17
	 * @author MBG
	 */
	private ZipFile getZipFile(File file) {
		try {
			return new ZipFile(file);
		}catch(Exception e) {}
		return null;
	}
	//#endregion
}







